﻿using System;
using System.Collections.Generic;
using Google.Protobuf;
using System.Reflection;
using System.Linq;
using GSE;
using GSE.protos;


namespace GSE.Rpc
{
    public class RequestHandler
    {

    }

    public class RequestHandlerRegister
    {
        readonly public Type ClassType;
        readonly public MethodInfo Method;
        readonly public Object HandlerObject;
        readonly public Type[] MethodParams;
        public RequestHandlerRegister(Type classType, MethodInfo method)
        {
            this.ClassType = classType;
            this.Method = method;
            HandlerObject = Activator.CreateInstance(classType);
            MethodParams = new Type[method.GetParameters().Length];
            for (var i = 0; i < MethodParams.Length; i++)
            {
                var p = method.GetParameters()[i].ParameterType;
                if (!typeof(IMessage).IsAssignableFrom(p))
                {
                    throw new Exception($"the {i}th parameter(\"{p.FullName}\") of {classType.FullName}::{method.Name}, must implements Protobuf.IMessage.");
                }

                if (!typeof(IMessage).IsAssignableFrom(method.ReturnType))
                {
                    throw new Exception($"the return type(\"{p.FullName}\") of {classType.FullName}::{method.Name}, must implements Protobuf.IMessage.");
                }
                MethodParams[i] = p;
            }
        }
    }

    public class RequestHandlerContainer
    {
        protected OptionContext optCtx;
        protected Dictionary<string, RequestHandlerRegister> handlerTypes = new Dictionary<string, RequestHandlerRegister>();

        public RequestHandlerContainer(OptionContext optCtx)
        {
            this.optCtx = optCtx;
        }
        protected ILogger logger
        {
            get { return Logger.Default; }
        }
        public Response Handle(Request req)
        {
            var exists = handlerTypes.TryGetValue(req.GetServiceName(), out var handler);
            if (!exists)
            {
                return new Response(ResponseStatus.ServiceNotExists, req.TransactionID, new byte[0]);
            }
            var args = new IMessage[handler.MethodParams.Length];
            try
            {
                for (var i = 0; i < args.Length;)
                {//TODO: 当前只支持一个参数.
                    args[i] = (IMessage)Activator.CreateInstance(handler.MethodParams[i]);
                    args[i].MergeFrom(req.Data);
                    break;
                }

                var result = handler.Method.Invoke(handler.HandlerObject, args) as IMessage;
                return new Response(ResponseStatus.Ok, req.TransactionID, result);
            }
            catch (Exception ex)
            {
                var exMsg = ex.Message + ":\n" + ex.StackTrace;
                while (true)
                {
                    if (ex.InnerException != null)
                    {
                        ex = ex.InnerException;
                        exMsg += "\n-------------------\n" + ex.Message + ":\n" + ex.StackTrace;

                    }
                    else
                    {
                        break;
                    }
                }

                logger.Warn($"request[{req.TransactionID}]({req.GetServiceName()}) handle exception: {exMsg}");
                var rpcErr = new RpcError();
                rpcErr.Code = 500;
                rpcErr.Msg = ex.Message;
                return new Response(ResponseStatus.ServiceProcessError, req.TransactionID, rpcErr.ToByteArray());
            }
        }

        public void Register(string name, RequestHandlerRegister reg)
        {
            logger.Debug($"Registered request handler: {name} ==>> {reg.ClassType.FullName}::{reg.Method.Name}");
            handlerTypes.Add(name, reg);
        }

        /// <summary>
        /// 通过解析Attribute,加载所有request handler信息.
        /// </summary>
        public void Load()
        {
            var types = Assembly.GetEntryAssembly().GetTypes();
            var requestHandlerBaseTypeName = typeof(RequestHandler).FullName;
            foreach (var type in types)
            {
                var t = type;
                while (t.BaseType != null)
                {
                    if (t.BaseType.FullName == requestHandlerBaseTypeName)
                    {
                        var tMethods = t.GetMethods().Where(m => m.GetCustomAttributes(typeof(RpcRegisterHandlerMethodAttribute), false).Length > 0);
                        foreach (var m in tMethods)
                        {
                            if (m.GetParameters().Length > 0)
                            {
                                if (m.GetParameters().Length != 1)
                                {
                                    throw new RequestHandlerRegisterException($"registered RPC request handler method '{m.Name}'should have  0 or one parameter, but got {m.GetParameters().Length}.");
                                }
                                else if (!typeof(IMessage).IsAssignableFrom(m.GetParameters()[0].ParameterType))
                                {
                                    throw new RequestHandlerRegisterException($"registered RPC request handler method '{m.Name}'should have one parameter that derived from {typeof(IMessage).FullName}.");
                                }
                            }

                            if (!typeof(IMessage).IsAssignableFrom(m.ReturnType))
                            {
                                throw new RequestHandlerRegisterException($"registered RPC request handler method '{m.Name}'should have return type that derived from {typeof(IMessage).FullName}.");
                            }

                            foreach (RpcRegisterHandlerMethodAttribute attr in m.GetCustomAttributes(typeof(RpcRegisterHandlerMethodAttribute), false))
                            {
                                var requestName = attr.RequestName == string.Empty ? t.Name + "." + m.Name : attr.RequestName;
                                var rhr = new RequestHandlerRegister(t, m);
                                Register(requestName, rhr);
                            }
                        }
                        break;
                    }
                    else
                    {
                        t = t.BaseType;
                    }
                }
            }
        }
    }
}
